import json
import getopt
import sys
import os
import subprocess
import time
import shutil
import threading
from bdf_parser import bdf_parser, filter_bdf, update_bdf
from op2_parser import parse_op2_items, extarct_op2_result
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

global end
end = False


class MyHandler(FileSystemEventHandler):
    def __init__(self, filename, dest_dir):
        self.filename = filename
        self.dest_dir = dest_dir

    def on_modified(self, event):
        # 检查是否是我们关注的文件被修改了
        if event.src_path == self.filename:
            print(f"{self.filename} has been modified. Copying to {self.dest_dir}")
            shutil.copy2(self.filename, self.dest_dir)

    def on_created(self, event):
        # 检查是否是我们关注的文件被创建了
        if event.src_path == self.filename:
            print(f"{self.filename} has been created. Copying to {self.dest_dir}")
            shutil.copy2(self.filename, self.dest_dir)


def file_monitor(filename, dest_dir):
    path_to_watch = os.path.dirname(filename)
    event_handler = MyHandler(filename, dest_dir)
    observer = Observer()
    observer.schedule(event_handler, path_to_watch, recursive=False)
    observer.start()
    try:
        while True:
            time.sleep(0.1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()


def get_args(argv):
    # 命令行参数解析
    sys_args = {}
    try:
        opts, args = getopt.getopt(
            argv,
            "c:d:p:m:o:e:",
            [
                "sim-config=",
                "driver-config=",
                "param-valid-file=",
                "model-dir=",
                "output-file-path=",
                "entry=",
            ],
        )
    except getopt.GetoptError:
        print(
            "Error: matlabDriver.py -c <config.json> -d <driver.config.json>"
            "-p <param_valid.json> -m <model_dir> -o <output.json file path> -e <entry file name>"
        )
        print(
            "  or: aspenPlusDriver.py --sim-config=<config.json> --driver-config=<driver.config.json>"
            "--param-valid-file=<config.json> --model-dir=<model_dir> --output-file-path=<file path> "
            "--entry=<entry file name>"
        )
        sys.Exit(1)
    for opt, arg in opts:
        if opt in ("-c", "--sim-config"):
            sys_args["simConfig"] = arg
        elif opt in ("-d", "--driver-config"):
            sys_args["driverConfig"] = arg
        elif opt in ["-p", "--param-valid-file"]:
            sys_args["paramValidFile"] = arg
        elif opt in ["-m", "--mode-dir"]:
            sys_args["modelDir"] = arg
        elif opt in ["-o", "--output-file-path"]:
            sys_args["outputFilePath"] = arg
        elif opt in ["-e", "--entry"]:
            sys_args["entry"] = arg
    return sys_args


def writeOutputFile(output_file_path, content):
    if not os.path.isdir(os.path.dirname(output_file_path)):
        os.mkdir(os.path.dirname(output_file_path))
    with open(output_file_path, "w") as file:
        file.write(content)


def parse_param(model_dir, output_file_path, entry):
    bdf_path = os.path.join(model_dir, entry)
    filter_bdf(bdf_path)
    result = bdf_parser(bdf_path)
    param_value = {"input": result, "output": []}
    # 基于op2文件解析输出参数,暂不在解析阶段自动运行bdf生成op2,需要上传op2文件
    for file in os.listdir(model_dir):
        if file.endswith("op2"):
            output = parse_op2_items(os.path.join(model_dir, file))
            param_value["output"] = output
    writeOutputFile(
        output_file_path,
        json.dumps({"success": True, "message": "", "data": param_value, }),
    )


def run_nastran(cmd, work_dir):
    # 运行pumplinx
    print("simulation start", flush=True)
    p = subprocess.Popen(
        cmd,
        shell=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        universal_newlines=True,
        cwd=work_dir,
    )
    while True:
        output = p.stdout.readline()
        if output == "" and p.poll() is not None:
            # print("simulation success")
            global end
            end = True
            return
        if output:
            print(output.strip())


def run(run_config_json_file, driver_path, output_file):
    global end
    with open(driver_path, "r") as file:
        driver_config = json.load(file)
        exe_path = driver_config["config"]["nastranExePath"]
        # option = driver_config["config"]["option"]
    with open(run_config_json_file, "r") as file:
        simulation_config = json.load(file)
    absolute_entry = os.path.join(
        simulation_config["model"]["file"], simulation_config["model"]["entry"]
    )
    work_dir = os.path.dirname(absolute_entry)
    temp_dir = os.path.join(work_dir, "tmp")
    if not os.path.isdir(temp_dir):
        os.mkdir(temp_dir)
    fname = os.path.splitext(simulation_config["model"]["entry"])[0] + ".f06"
    src = os.path.join(work_dir, fname)
    monitor_thread = threading.Thread(
        target=file_monitor, args=(src, temp_dir))
    monitor_thread.daemon = True
    monitor_thread.start()
    cmd = f"{exe_path} {absolute_entry}"
    for item in simulation_config["extendedProperty"]["runOption"].split(","):
        k = item.split("=")[0].strip()
        v = item.split("=")[1].strip()
        cmd += f" {k}={v}"
    # Todo 更新输入参数
    update_bdf(absolute_entry, simulation_config["parameters"])
    run_nastran(cmd, work_dir)
    while True:
        if end:
            params_value = {
                "success": True,
                "message": "",
                "data": {"simulationResultDir": "", "specifiedParameters": []},
            }
            with open(src, "r") as file:
                content = file.read()
                if "FATAL" in content:
                    params_value["success"] = False
            with open(src, "r") as file:
                lines = file.readlines()
                start_index, end_index = 0, -1
                for index, line in enumerate(lines):
                    if "FATAL" in line:
                        start_index = index
                        break
                for index, line in enumerate(lines):
                    if "END OF JOB" in line:
                        end_index = index
                        break
                if params_value["success"] is False:
                    params_value["message"] = "\n".join(
                        lines[start_index: end_index])
            if params_value["success"] is True:
                print("simulation success")
                # 回传工程文件
                # if simulation_config["result"]["uploadRawSimResult"]:
                #     params_value["data"]["simulationResultDir"] = work_dir
                # 提取指定的输出参数
                for output in simulation_config["result"]["specifiedOutputParams"]:
                    for file in os.listdir(work_dir):
                        if file.endswith("op2"):
                            op2_path = os.path.join(work_dir, file)
                            result = extarct_op2_result(op2_path, output)
                            output["value"] = result
                            params_value["data"]["specifiedParameters"].append(
                                output
                            )
            writeOutputFile(output_file, json.dumps(params_value))
            return


if __name__ == "__main__":
    mode = sys.argv[1]
    sysArgs = get_args(sys.argv[2:])
    print(f"mode = {mode},sysArgs = {sysArgs}", flush=True)
    if mode == "parse":
        # 参数解析模式
        print("parse param from model", flush=True)
        model_path = sysArgs.get("modelDir")
        output_file = sysArgs.get("outputFilePath")
        entry = sysArgs.get("entry")
        if model_path is None or output_file is None or entry is None:
            print(
                "model path and output file path and entry path is required",
                flush=True,
            )
            sys.exit(1)
        try:
            parse_param(model_path, output_file, entry)
        except Exception as e:
            writeOutputFile(
                output_file, json.dumps({"success": False, "message": e})
            )
    elif mode == "run":
        # 运行计算模式
        run_config_json_file = sysArgs.get("simConfig")
        if run_config_json_file is None:
            print("input file is required", flush=True)
            sys.exit(1)
        driver_path = sysArgs.get("driverConfig")
        if driver_path is None:
            driver_path = "./driver.config.json"
        output_file = sysArgs.get("outputFilePath")
        run(run_config_json_file, driver_path, output_file)
    else:
        print(f"unsupport sub command: ${mode}", flush=True)
        sys.exit(1)
